(文末有福利)
websocket通知流程 解析 我们不能保证用户B和用户A都处于连接状态,但是通常情况下,用户B至少是连接状态,用户A不一定跟server保持连接;
任一用户都不止对应一个客户端。换言之,用户A和用户B都可能打开了多个tab页,对于一个tab页,就会有一个独立的fd标识,我们这里认为任一用户只有最新的fd有效,或者你可以认为用户所有的tab页的连接都有效;
因为没有用户系统,我们以get传递的参数uid为标识,uid=100视为用户A,uid=101视为用户B;
我们模拟的tab页包将会包含一个输入内容的文本框、一个输入目标uid的input和一个发送的按钮以满足需求。
操作流程分析: 用户A($_GET['uid'] = 100)在某个tab页的输入框输入"回复xxx的内容"字样后,点击发送
用户B($_GET['uid'] = 101)如果处于连接状态,则alert提醒用户B,他的评论被回复了
Server 后端启动:php server.php
class CommentServer {private $_serv;public $key = '^manks.top&swoole$';// 用户id和fd对应的映射,key => value,key是用户的uid,value是用户的fdpublic $user2fd = [];public function __construct() {$this->_serv = new swoole_websocket_server("127.0.0.1", 9501);$this->_serv->set(['worker_num' => 1,'heartbeat_check_interval' => 60,'heartbeat_idle_time' => 125,]);$this->_serv->on('open', [$this, 'onOpen']);$this->_serv->on('message', [$this, 'onMessage']);$this->_serv->on('close', [$this, 'onClose']);}/*** @param $serv* @param $request* @return mixed*/public function onOpen($serv, $request) {// 连接授权$accessResult = $this->checkAccess($serv, $request);if (!$accessResult) {return false;}// 始终把用户最新的fd跟uid映射在一起if (array_key_exists($request->get['uid'], $this->user2fd)) {$existFd = $this->user2fd[$request->get['uid']];$this->close($existFd, 'uid exists.');$this->user2fd[$request->get['uid']] = $request->fd;return false;} else {$this->user2fd[$request->get['uid']] = $request->fd;}}/*** @param $serv* @param $frame* @return mixed*/public function onMessage($serv, $frame) {// 校验数据的有效性,我们认为数据被`json_decode`处理之后是数组并且数组的`event`项非空才是有效数据// 非有效数据,关闭该连接$data = $frame->data;$data = json_decode($data, true);if (!$data || !is_array($data) || empty($data['event'])) {$this->close($frame->fd, 'data format invalidate.');return false;}// 根据数据的`event`项,判断要做什么,`event`映射到当前类具体的某一个方法,方法不存在则关闭连接$method = $data['event'];if (!method_exists($this, $method)) {$this->close($frame->fd, 'event is not exists.');return false;}$this->$method($frame->fd, $data);}public function onClose($serv, $fd) {echo "client {$fd} closed.\n";}/*** 校验客户端连接的合法性,无效的连接不允许连接* @param $serv* @param $request* @return mixed*/public function checkAccess($serv, $request) {// get不存在或者uid和token有一项不存在,关闭当前连接if (!isset($request->get) || !isset($request->get['uid']) || !isset($request->get['token'])) {$this->close($request->fd, 'access faild.');return false;}$uid = $request->get['uid'];$token = $request->get['token'];// 校验token是否正确,无效关闭连接if (md5(md5($uid) . $this->key) != $token) {$this->close($request->fd, 'token invalidate.');return false;}return true;}/*** @param $fd* @param $message* 关闭$fd的连接,并删除该用户的映射*/public function close($fd, $message = '') {// 关闭连接$this->_serv->close($fd);// 删除映射关系if ($uid = array_search($fd, $this->user2fd)) {unset($this->user2fd[$uid]);}}public function alertTip($fd, $data) {// 推送目标用户的uid非真或者该uid尚无保存的映射fd,关闭连接if (empty($data['toUid']) || !array_key_exists($data['toUid'], $this->user2fd)) {$this->close($fd);return false;}$this->push($this->user2fd[$data['toUid']], ['event' => $data['event'], 'msg' => '收到一条新的回复.']);}/*** @param $fd* @param $message*/public function push($fd, $message) {if (!is_array($message)) {$message = [$message];}$message = json_encode($message);// push失败,closeif ($this->_serv->push($fd, $message) == false) {$this->close($fd);}}public function start() {$this->_serv->start();} }$server = new CommentServer; $server->start();
前端页面 client.php
发送内容: 发送给谁:发送
一步一步教你操作
回复关键字获取资源链接:
wokerman实战之PHP在线客服:wokerman